from __future__ import absolute_import
import logging
import forest.config
from os.path import dirname, join
from forest.plugins import PluginRegistry, search_plugins, PluginMeta, registry
from forest.registry import command_registry
from forest.signals import Signal
logger = logging.getLogger('forest.command')

def search_commands():
    search_plugins(join(dirname(__file__), 'commands'), 'forest.commands.')

    # register the config types of each command in the global config
    for key, command in command_registry.items():
        forest.config.Config.DEFAULT_CONFIG_TYPES.update(command.CONFIG_TYPES)

    return command_registry

class CommandMeta(type):
    """Metaclass for adding a plugin to the registry"""
    def __init__(cls, name, bases, dic):
        super(CommandMeta, cls).__init__(name, bases, dic)

        if cls is PluginMeta:
            return

        register_name = dic.get('__register__')
        if not register_name:
            raise Exception("Could not find declared __register__ property on Stage class %s" % cls.__name__)

        registry.register(register_name, cls)

        command_registry[register_name] = cls


class Command(object):
    ''' The command class is used to encapsulate forest's various points of configuration and execution.

        Subclasses of this are automatically registered using metaclasses.
    '''
    __metaclass__ = CommandMeta
    __register__ = 'commands.Command'

    CONFIG_TYPES = {}

    SIGNAL_COMMAND_START = Signal()
    ''' This signal is called just before Command.run() is called. '''

    SIGNAL_COMMAND_PROGRESS = Signal()
    ''' This signal is called during command progress so that interfaces can be notified.

        Its signature should look like:

        handle_progress(command, percent=None, level=None, message='')
    '''

    SIGNAL_COMMAND_FINISH = Signal()
    ''' This signal is called just after Command.run() is called. '''

    requires = None

    # this is a list of dotted config file keys which the config file must have in order for this command to run
    requires_config = []

    def __init__(self, args):
        ''' args - An object (dict) defining the arguments to run this command with.
        '''
        self.args = args

        if self.requires is None:
            self.requires = []

    def run(self, config):
        ''' Run the command, using the passed configuration object.
        '''
        raise NotImplementedError

def default_handle_command_start(command):
    logger.info('### Running command %s ###' % command.__register__)

def default_handle_command_finish(command):
    #logger.info('### Finished stage %s ###' % command.__register__)
    pass

def default_handle_command_progress(command, percent=None, level=None, message=''):

    if not level:
        level = logging.INFO

    if message:
        logger.log(level, message)


Command.SIGNAL_COMMAND_START.connect(default_handle_command_start)
Command.SIGNAL_COMMAND_PROGRESS.connect(default_handle_command_progress)
Command.SIGNAL_COMMAND_FINISH.connect(default_handle_command_finish)


class Runner(list):
    ''' Runs the specified stage defined in config.

        config - the global configuration object.
        commandconflist - should be a list of dicts representing commands.
    '''
    def run(self, config, sequence):

        for short_command in sequence:

            # try to find the arguments
            command_args = config.get(short_command, {})

            command_name = 'commands.%s' % short_command

            command_class = registry.get(command_name)


            if command_class.requires_config:
                notfound = []
                for key in command_class.requires_config:
                    if key not in config:
                        notfound.append(key)

                if notfound:
                    logger.warn('### Skipping command %s due to missing keys: %s ###' % (command_name, ', '.join(notfound)))
                    continue

            command_instance = command_class(command_args)

            Command.SIGNAL_COMMAND_START(command_instance)
            command_instance.run(config)
            Command.SIGNAL_COMMAND_FINISH(command_instance)


        #logger.info('Ran %s stages: ' % len(commandconflist))
        #for commandconf in commandconflist:
        #    logger.info('\t- %s' % commandconf.get('name'))

